from math import sin, pi
import time

from PySide6.QtGui import QColor
from PySide6.QtCore import QPoint

from .cfg_util import config_instance as ci

class AnimatedValue:
    '''Class for creating smooth animated values with easing functions'''
    
    def __init__(self, animation_time: int, extra_animation: bool) -> None:
        self.ani_time = animation_time  # Animation duration in milliseconds
        self.extra_animation = extra_animation  # Whether to add extra bounce effect

        # Animation state variables
        self._start_time = 0
        self._target_difference = 0
        self._target_value = 0
        self._current_value = 0
        self._animation_base = 0

        self._last_update = time.perf_counter()

    @property
    def target_value(self) -> float:
        return self._target_value
    
    @target_value.setter
    def target_value(self, value: float) -> None:
        '''Set target value and initialize animation'''
        if self._target_value != value:
            self._animation_base = self._current_value
            self._start_time = time.perf_counter() * 1000
            self._target_difference = value - self._current_value
            self._target_value = value

    @property
    def current_value(self) -> float:
        '''Calculate current animated value based on elapsed time'''
        elapsed = min(time.perf_counter() * 1000 - self._start_time, self.ani_time)
        animation_progress = elapsed / self.ani_time

        # Calculate extra animation for bounce effect
        extra = min(time.perf_counter() * 1000 - (self._start_time - self.ani_time / 5), self.ani_time)
        extra_progress = extra / self.ani_time

        # Combine main animation with optional bounce effect
        self._current_value = self._animation_base + (self._target_difference * sin(sin(animation_progress * pi / 2) * pi / 2)) + (
            self._target_difference * sin(extra_progress * pi) * 0.2 * (0 if not self.extra_animation else 1)
        )

        # Snap to target when close enough to prevent jitter
        if abs(self._current_value - self._target_value) < self._target_difference / 10:
            self._current_value = self._target_value

        return self._current_value
    
class AnimatedColor:
    '''Class for creating smooth animated colors'''

    def __init__(self, animation_time: int) -> None:
        self._red_animation = AnimatedValue(animation_time, False)
        self._green_animation = AnimatedValue(animation_time, False)
        self._blue_animation = AnimatedValue(animation_time, False)
        self._alpha_animation = AnimatedValue(animation_time, False)

    @property
    def target_color(self) -> QColor:
        return QColor(
            int(self._red_animation.current_value),
            int(self._green_animation.current_value),
            int(self._blue_animation.current_value),
            int(self._alpha_animation.current_value)
        )
    
    @target_color.setter
    def target_color(self, color: QColor) -> None:
        self._red_animation.target_value = color.red()
        self._green_animation.target_value = color.green()
        self._blue_animation.target_value = color.blue()
        self._alpha_animation.target_value = color.alpha()

    @property
    def current_color(self) -> QColor:
        return QColor(
            int(self._red_animation.current_value),
            int(self._green_animation.current_value),
            int(self._blue_animation.current_value),
            int(self._alpha_animation.current_value)
        )
    
class AnimatedPoint:
    '''Class for creating smooth animated points'''

    def __init__(self, animation_time: int, extra_animation: bool) -> None:
        self._x_animation = AnimatedValue(animation_time, extra_animation)
        self._y_animation = AnimatedValue(animation_time, extra_animation)

    @property
    def target_point(self) -> QPoint:
        return QPoint(
            int(self._x_animation.current_value),
            int(self._y_animation.current_value)
        )
    
    @target_point.setter
    def target_point(self, point: QPoint) -> None:
        self._x_animation.target_value = point.x()
        self._y_animation.target_value = point.y()

    @property
    def current_point(self) -> QPoint:
        return QPoint(
            int(self._x_animation.current_value),
            int(self._y_animation.current_value)
        )